#include "ms3d.h"
#include <stdio.h>
#include <stdlib.h>

#define DEBUG

#ifdef DEBUG
	#define printd printf
#else
	#define printd(...) 
#endif

void *loadData(int dataSize, int count, FILE *f)
{
	void *data = malloc(dataSize*count);
	fread(data, dataSize, count, f);

	return data;
}

void *loadCountAndData(int dataSize, word *count, FILE *f)
{
	fread(count, sizeof(word), 1, f);

	return loadData(dataSize, *count, f);
}

int checkHeader(FILE *f)
{
	ms3d_header_t header;
	fread(&header, sizeof(ms3d_header_t), 1, f);

	return 1;
}

ms3d_vertex_t *loadVertexData(FILE *f, word *count)
{
	return loadCountAndData(sizeof(ms3d_vertex_t), count, f);
}

ms3d_triangle_t *loadTrianglesData(FILE *f, word *count)
{
	return loadCountAndData(sizeof(ms3d_triangle_t), count, f);
}

ms3d_group_t *loadGroupsData(FILE *f, word *count)
{
	int i;

	typedef struct 
	{
		GROUP_INFO   
	} __attribute__ ((__packed__)) group_info;

	fread(count, sizeof(word), 1, f);
	ms3d_group_t *groupsData = malloc(sizeof(ms3d_group_t) * (*count));

	for(i=0; i<*count; i++) {
		fread(&groupsData[i], sizeof(group_info), 1, f);
		groupsData[i].triangleIndices = loadData(sizeof(word), groupsData[i].numtriangles, f);
		fread(&(groupsData[i].materialIndex), sizeof(char), 1, f);
	}

	return groupsData;
}

ms3d_material_t *loadMaterialsData(FILE *f, word *count)
{
	return loadCountAndData(sizeof(ms3d_material_t), count, f);
}

ms3d_anim_info_t *loadAnimInfo(FILE *f)
{
	return loadData(sizeof(ms3d_anim_info_t), 1, f);
}

ms3d_joint_t *loadJointsData(FILE *f, word *count)
{
	int i;

	typedef struct
	{
		JOINT_INFO
	} __attribute__ ((__packed__)) joint_info;


	fread(count, sizeof(word), 1, f);
	ms3d_joint_t *jointsData = malloc(sizeof(ms3d_joint_t) * (*count));

	for(i=0; i<*count; i++) {
		fread(&jointsData[i], sizeof(joint_info), 1, f);
		jointsData[i].keyFramesRot = loadData(sizeof(ms3d_keyframe_rot_t), jointsData[i].numKeyFramesRot, f);
		jointsData[i].keyFramesTrans = loadData(sizeof(ms3d_keyframe_pos_t), jointsData[i].numKeyFramesTrans, f);
	}

	return jointsData;
}

void printVertexData(word nVertices, ms3d_vertex_t *vertexData)
{
	int i;

	printf("NVertices: %d\n",nVertices);
	for(i=0; i<nVertices; i++) {
		printf("%d %f,%f,%f %d %d\n",vertexData[i].flags,vertexData[i].vertex[0],vertexData[i].vertex[1],vertexData[i].vertex[2],vertexData[i].boneId,vertexData[i].referenceCount);
	}
}

void printTrianglesData(word nTriangles, ms3d_triangle_t *trianglesData)
{
	int i;

	printf("NTriangles: %d\n",nTriangles);
	for(i=0; i<nTriangles; i++) {
		printf("%d [%d %d %d] [%f %f %f],[%f %f %f],[%f %f %f] [%f %f %f] [%f %f %f] %d %d\n",
trianglesData[i].flags,
trianglesData[i].vertexIndices[0],trianglesData[i].vertexIndices[1],trianglesData[i].vertexIndices[2],
trianglesData[i].vertexNormals[0][0],trianglesData[i].vertexNormals[0][1],trianglesData[i].vertexNormals[0][2],
trianglesData[i].vertexNormals[1][0],trianglesData[i].vertexNormals[1][1],trianglesData[i].vertexNormals[1][2],
trianglesData[i].vertexNormals[2][0],trianglesData[i].vertexNormals[2][1],trianglesData[i].vertexNormals[2][2],
trianglesData[i].s[0],trianglesData[i].s[1],trianglesData[i].s[2],
trianglesData[i].t[0],trianglesData[i].t[1],trianglesData[i].t[2],
trianglesData[i].smoothingGroup,trianglesData[i].groupIndex);
	}
}

void printGroupsData(word nGroups, ms3d_group_t *groupsData)
{
	int i;

	printf("NGroups: %d\n",nGroups);
	for(i=0; i<nGroups; i++) {
		printf("%d %s %d %d\n", groupsData[i].flags, groupsData[i].name, groupsData[i].numtriangles, groupsData[i].materialIndex);
		int j;
		for(j=0; j<groupsData[i].numtriangles; j++) {
			printf("%d ",groupsData[i].triangleIndices[j]);
		}
	}
}

void printMaterialsData(word nMaterials, ms3d_material_t *materialsData)
{
	int i;

	printf("NMaterials: %d\n",nMaterials);
	for(i=0; i<nMaterials; i++) {
		printf("%s [%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f] %f %f %d %s %s\n",
materialsData[i].name,
materialsData[i].ambient[0],materialsData[i].ambient[1],materialsData[i].ambient[2],materialsData[i].ambient[3],
materialsData[i].diffuse[0],materialsData[i].diffuse[1],materialsData[i].diffuse[2],materialsData[i].diffuse[3],
materialsData[i].specular[0],materialsData[i].specular[1],materialsData[i].specular[2],materialsData[i].specular[3],
materialsData[i].emissive[0],materialsData[i].emissive[2],materialsData[i].emissive[3],materialsData[i].emissive[4],
materialsData[i].shininess,materialsData[i].transparency,materialsData[i].mode,
materialsData[i].texture,materialsData[i].alphamap);
	}
}

void printJointsData(word nJoints, ms3d_joint_t *jointsData)
{
	int i;

	printf("NJoints: %d\n",nJoints);
	for(i=0; i<nJoints; i++) {
		printf("%d %s %s [%f %f %f] [%f %f %f] %d %d\n",
jointsData[i].flags,jointsData[i].name,jointsData[i].parentName,
jointsData[i].rotation[0],jointsData[i].rotation[1],jointsData[i].rotation[2],
jointsData[i].position[0],jointsData[i].position[1],jointsData[i].position[2],
jointsData[i].numKeyFramesRot,jointsData[i].numKeyFramesTrans);

		int j;
		for(j=0; j<jointsData[i].numKeyFramesRot; j++) {
			printf("%f [%f %f %f]\n",
jointsData[i].keyFramesRot[j].time,
jointsData[i].keyFramesRot[j].rotation[0],jointsData[i].keyFramesRot[j].rotation[1],jointsData[i].keyFramesRot[j].rotation[2]);
		}

		for(j=0; j<jointsData[i].numKeyFramesTrans; j++) {
			printf("%f [%f %f %f]\n",
jointsData[i].keyFramesTrans[j].time,
jointsData[i].keyFramesTrans[j].position[0],jointsData[i].keyFramesTrans[j].position[1],jointsData[i].keyFramesTrans[j].position[2]);
		}
	}
}

int findJointIndex(word nJoints, ms3d_joint_t *jointsData, char *name) {
	int i;

	for(i=0; i<nJoints; i++) {
		if(!strcmp(name,jointsData[i].name)) {
			return i;
		}
	}

	return -1;
}

typedef struct
{
	float pos[3];
	float norm[3];
	float s, t;
	word boneId;
} myVertex;

myVertex *verts = 0;
word nVerts = 0;

word addVertex(float *pos, float *norm, float s, float t, word boneId)
{
	word i;

	for(i=0; i<nVerts; i++) {
		if(verts[i].pos[0] != pos[0]) continue;
		if(verts[i].pos[1] != pos[1]) continue;
		if(verts[i].pos[2] != pos[2]) continue;
		if(verts[i].norm[0] != norm[0]) continue;
		if(verts[i].norm[1] != norm[1]) continue;
		if(verts[i].norm[2] != norm[2]) continue;
		if(verts[i].s != s) continue;
		if(verts[i].t != t) continue;
		if(verts[i].boneId != boneId) continue;

		return i;
	}

	myVertex *newPtr = realloc(verts, sizeof(myVertex)*(nVerts+1));
	if(newPtr) {
		verts = newPtr;
		verts[nVerts].pos[0] = pos[0];
		verts[nVerts].pos[1] = pos[1];
		verts[nVerts].pos[2] = pos[2];
		verts[nVerts].norm[0] = norm[0];
		verts[nVerts].norm[1] = norm[1];
		verts[nVerts].norm[2] = norm[2];
		verts[nVerts].s = s;
		verts[nVerts].t = t;
		verts[nVerts].boneId = boneId;
		++nVerts;
	}

	return nVerts-1;
}

void exportMaterials(word nMaterials, ms3d_material_t *materialsData, FILE *f)
{
	printd("%d\n",nMaterials);
	fwrite(&nMaterials, sizeof(word), 1, f);

	int m;
	for(m=0; m<nMaterials; m++) {
                printd("[%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f] %f %f %s %s\n",
materialsData[m].ambient[0],materialsData[m].ambient[1],materialsData[m].ambient[2],materialsData[m].ambient[3],
materialsData[m].diffuse[0],materialsData[m].diffuse[1],materialsData[m].diffuse[2],materialsData[m].diffuse[3],
materialsData[m].specular[0],materialsData[m].specular[1],materialsData[m].specular[2],materialsData[m].specular[3],
materialsData[m].emissive[0],materialsData[m].emissive[2],materialsData[m].emissive[3],materialsData[m].emissive[4],
materialsData[m].shininess,materialsData[m].transparency,
materialsData[m].texture,materialsData[m].alphamap);
		fwrite(materialsData[m].ambient, sizeof(float), 4, f);
		fwrite(materialsData[m].diffuse, sizeof(float), 4, f);
		fwrite(materialsData[m].specular, sizeof(float), 4, f);
		fwrite(materialsData[m].emissive, sizeof(float), 4, f);
		fwrite(&materialsData[m].shininess, sizeof(float), 1, f);
		fwrite(&materialsData[m].transparency, sizeof(float), 1, f);
		fprintf(f,"%s\0%s\0",materialsData[m].texture,materialsData[m].alphamap);
	}
}

void exportJoints(word nJoints, ms3d_joint_t *jointsData, FILE *f)
{
	printd("%d\n",nJoints);
	fwrite(&nJoints, sizeof(word), 1, f);

	int i;
	for(i=0; i<nJoints; i++) {
		int jointParentIndex = findJointIndex(nJoints, jointsData, jointsData[i].parentName);
		printd("%d [%f %f %f] [%f %f %f] %d %d\n",
jointParentIndex,
jointsData[i].rotation[0],jointsData[i].rotation[1],jointsData[i].rotation[2],
jointsData[i].position[0],jointsData[i].position[1],jointsData[i].position[2],
jointsData[i].numKeyFramesRot,jointsData[i].numKeyFramesTrans);
		fwrite(&jointParentIndex, sizeof(int), 1, f);
		fwrite(jointsData[i].rotation, sizeof(float), 3, f);
		fwrite(jointsData[i].position, sizeof(float), 3, f);

		if(jointsData[i].numKeyFramesRot!=jointsData[i].numKeyFramesTrans) {
			printf("ERROR: numKeyframesRot != numKeyframesTrans!!!!\n");
			exit(0);
		}

		fwrite(&jointsData[i].numKeyFramesRot, sizeof(int), 1, f);

		int j;
		for(j=0; j<jointsData[i].numKeyFramesRot; j++) {
			if(jointsData[i].keyFramesRot[j].time != jointsData[i].keyFramesTrans[j].time) {
				printf("ERROR: Different time of rotation and translation keyframe!!!\n");
				exit(0);
			}
			printd("%f [%f %f %f] [%f %f %f]\n",
jointsData[i].keyFramesRot[j].time,
jointsData[i].keyFramesRot[j].rotation[0],jointsData[i].keyFramesRot[j].rotation[1],jointsData[i].keyFramesRot[j].rotation[2],
jointsData[i].keyFramesTrans[j].position[0],jointsData[i].keyFramesTrans[j].position[1],jointsData[i].keyFramesTrans[j].position[2]);
			fwrite(&jointsData[i].keyFramesRot[j].time, sizeof(float), 1, f);
			fwrite(jointsData[i].keyFramesRot[j].rotation, sizeof(float), 3, f);
			fwrite(jointsData[i].keyFramesTrans[j].position, sizeof(float), 3, f);
		}
	}
}

void exportVertex(float *pos, float *norm, float s, float t, FILE *f)
{
	fwrite(pos, sizeof(float), 3, f);

	fwrite(norm, sizeof(float), 3, f);
/*
	signed char norm8bit[3];
	norm8bit[0] = norm[0]*127;
	norm8bit[1] = norm[1]*127;
	norm8bit[2] = norm[2]*127;
	fwrite(norm8bit, sizeof(signed char), 3, f);
*/

	fwrite(&s, sizeof(float), 1, f);
	fwrite(&t, sizeof(float), 1, f);
}

int nVertsTotal = 0;
void exportData(word nGroups, ms3d_group_t *groupsData, ms3d_triangle_t *trianglesData, ms3d_vertex_t *vertexData,
		word nMaterials, ms3d_material_t *materialsData, word nJoints, ms3d_joint_t *jointsData, FILE *f)
{
	printd("%d\n",nGroups);
	fwrite(&nGroups, sizeof(word), 1, f);

	int g,t;
	for(g=0; g<nGroups; g++) {
		printd("%d %d",groupsData[g].materialIndex,groupsData[g].numtriangles);
		fwrite(&groupsData[g].materialIndex, sizeof(word), 1, f);
		fwrite(&groupsData[g].numtriangles, sizeof(word), 1, f);

		for(t=0; t<groupsData[g].numtriangles; t++) {
			ms3d_triangle_t *tri = &trianglesData[groupsData[g].triangleIndices[t]];

			float *v1Coord = vertexData[tri->vertexIndices[0]].vertex;
			float *v1N = tri->vertexNormals[0];
			printd("%f %f %f %f %f %f %f %f\n",v1Coord[0],v1Coord[1],v1Coord[2],v1N[0],v1N[1],v1N[2],tri->s[0],tri->t[0]);
			exportVertex(v1Coord, v1N, tri->s[0], tri->t[0], f);
			addVertex(v1Coord,v1N,tri->s[0],tri->t[0],vertexData[tri->vertexIndices[0]].boneId);

			float *v2Coord = vertexData[tri->vertexIndices[1]].vertex;
			float *v2N = tri->vertexNormals[1];
			printd("%f %f %f %f %f %f %f %f\n",v2Coord[0],v2Coord[1],v2Coord[2],v2N[0],v2N[1],v2N[2],tri->s[1],tri->t[1]);
			exportVertex(v2Coord, v2N, tri->s[1], tri->t[1], f);
			addVertex(v2Coord,v2N,tri->s[1],tri->t[1],vertexData[tri->vertexIndices[1]].boneId);

			float *v3Coord = vertexData[tri->vertexIndices[2]].vertex;
			float *v3N = tri->vertexNormals[2];
			printd("%f %f %f %f %f %f %f %f\n",v3Coord[0],v3Coord[1],v3Coord[2],v3N[0],v3N[1],v3N[2],tri->s[2],tri->t[2]);
			exportVertex(v3Coord, v3N, tri->s[2], tri->t[2], f);
			addVertex(v3Coord,v3N,tri->s[2],tri->t[2],vertexData[tri->vertexIndices[2]].boneId);

			nVertsTotal+=3;
		}

		for(t=0; t<groupsData[g].numtriangles; t++) {
			ms3d_triangle_t *tri = &trianglesData[groupsData[g].triangleIndices[t]];

			printd("%d %d %d\n",
				vertexData[tri->vertexIndices[0]].boneId,
				vertexData[tri->vertexIndices[1]].boneId,
				vertexData[tri->vertexIndices[2]].boneId);
			fwrite(&vertexData[tri->vertexIndices[0]].boneId, sizeof(word), 1, f);
			fwrite(&vertexData[tri->vertexIndices[1]].boneId, sizeof(word), 1, f);
			fwrite(&vertexData[tri->vertexIndices[2]].boneId, sizeof(word), 1, f);
		}

	}

	exportMaterials(nMaterials, materialsData, f);
	exportJoints(nJoints, jointsData, f);	

	printd("NVERTS_DIF %d, NVERTS_TOTAL %d\n", nVerts, nVertsTotal);
}


void exportDataIndexed(word nGroups, ms3d_group_t *groupsData, ms3d_triangle_t *trianglesData, ms3d_vertex_t *vertexData,
			word nMaterials, ms3d_material_t *materialsData, word nJoints, ms3d_joint_t *jointsData, FILE *f)
{
	printd("%d\n",nGroups);
	fwrite(&nGroups, sizeof(word), 1, f);

	int g,t;
	for(g=0; g<nGroups; g++) {
		printd("%d %d",groupsData[g].materialIndex,groupsData[g].numtriangles);
		fwrite(&groupsData[g].materialIndex, sizeof(word), 1, f);
		fwrite(&groupsData[g].numtriangles, sizeof(word), 1, f);

		for(t=0; t<groupsData[g].numtriangles; t++) {
			ms3d_triangle_t *tri = &trianglesData[groupsData[g].triangleIndices[t]];

			float *v1Coord = vertexData[tri->vertexIndices[0]].vertex;
			float *v1N = tri->vertexNormals[0];
			word vIndex1 = addVertex(v1Coord,v1N,tri->s[0],tri->t[0],vertexData[tri->vertexIndices[0]].boneId);

			float *v2Coord = vertexData[tri->vertexIndices[1]].vertex;
			float *v2N = tri->vertexNormals[1];
			word vIndex2 = addVertex(v2Coord,v2N,tri->s[1],tri->t[1],vertexData[tri->vertexIndices[1]].boneId);

			float *v3Coord = vertexData[tri->vertexIndices[2]].vertex;
			float *v3N = tri->vertexNormals[2];
			word vIndex3 = addVertex(v3Coord,v3N,tri->s[2],tri->t[2],vertexData[tri->vertexIndices[2]].boneId);

			printd("%d %d %d\n",vIndex1,vIndex2,vIndex3);
			fwrite(&vIndex1, sizeof(word), 1, f);
			fwrite(&vIndex2, sizeof(word), 1, f);
			fwrite(&vIndex3, sizeof(word), 1, f);
		}
	}

	printd("%d\n", nVerts);
	fwrite(&nVerts, sizeof(word), 1, f);

	int v;
	for(v=0; v<nVerts; v++) {
		printd("[%f %f %f] [%f %f %f] [%f %f]\n",verts[v].pos[0],verts[v].pos[1],verts[v].pos[2],
			verts[v].norm[0],verts[v].norm[1],verts[v].norm[2],verts[v].s,verts[v].t);
		exportVertex(verts[v].pos, verts[v].norm, verts[v].s, verts[v].t, f);
	}
	for(v=0; v<nVerts; v++) {
		printd("%d\n", verts[v].boneId);
		fwrite(&verts[v].boneId, sizeof(word), 1, f);
	}


	exportMaterials(nMaterials, materialsData, f);
	exportJoints(nJoints, jointsData, f);	
}

void main(int argc, char **argv)
{
	FILE *fIn = fopen(argv[1],"r");

	checkHeader(fIn);

	word nVertices;
	ms3d_vertex_t *vertexData = loadVertexData(fIn, &nVertices);
//	printVertexData(nVertices, vertexData);

	word nTriangles;
	ms3d_triangle_t *trianglesData = loadTrianglesData(fIn, &nTriangles);
//	printTrianglesData(nTriangles, trianglesData);

	word nGroups;
	ms3d_group_t *groupsData = loadGroupsData(fIn, &nGroups);
//	printGroupsData(nGroups, groupsData);

	word nMaterials;
	ms3d_material_t *materialsData = loadMaterialsData(fIn, &nMaterials);
//	printMaterialsData(nMaterials, materialsData);

	ms3d_anim_info_t *animInfo = loadAnimInfo(fIn);

	word nJoints;
	ms3d_joint_t *jointsData = loadJointsData(fIn, &nJoints);
//	printJointsData(nJoints, jointsData);

	fclose(fIn);

	FILE *fOut = fopen(argv[2],"w");
	exportDataIndexed(nGroups, groupsData, trianglesData, vertexData, nMaterials, materialsData, nJoints, jointsData, fOut);
	fclose(fOut);
}
